package com.sanhuo.triild;


import com.sanhuo.lib.exception.GlobalException;
import com.sanhuo.triild.annotation.*;
import com.sanhuo.triild.page.Page;
import com.sanhuo.triild.page.PageSearch;
import com.sanhuo.triild.page.PageUtil;
import com.sanhuo.triild.utils.ConnectionUtil;
import com.sanhuo.utils.basic.CollectionUtil;
import com.sanhuo.utils.basic.ReflectionUtil;
import com.sanhuo.utils.basic.StringUtil;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import static com.sanhuo.triild.utils.AnnotationUtil.*;
import static com.sanhuo.triild.utils.ResultsUtil.mapToEntity;
import static com.sanhuo.triild.utils.ResultsUtil.toResults;

public class Session {
    private static Logger log = Logger.getLogger(Session.class.getName());
    private final String SQL = "sql";
    private final String PARAMS = "params";

    private Connection getConnection() {
        return ConnectionUtil.getConncetion();
    }

    /**
     * 根据方法上注解的不同执行不同的语句
     *
     * @param method
     * @param args
     * @return
     * @throws SQLException
     */
    public Object excute(Method method, Object[] args, Type type) {
        if (isSelect(method)) {
            return select(method, args, type);
        }
        if (isInsert(method)) {
            return insert(method, args, type);
        }
        if (isUpdate(method)) {
            return update(method, args, type);
        }
        if (isDelete(method)) {
            return delete(method, args, type);
        }

        return null;
    }

    private Object delete(Method method, Object[] args, Type type) {
        Delete delete = getDeleteAnnotation(method);
        DeleteProvider deleteProvider = getDeleteProviderAnnotation(method);
        String sql = null;
        /*根据method的注解不同来获取对应的sql语句*/
        if (delete != null) {
            sql = delete.value();
        } else if (deleteProvider != null) {
            sql = providerBuilder(method, args, type);
        }
        return update(sql, method, args);
    }

    /**
     * 更新操作
     *
     * @param method
     * @param args
     * @return
     */
    private Object update(Method method, Object[] args, Type type) {

        Update update = getUpdateAnnotation(method);
        UpdateProvider updateProvider = getUpdateProviderAnnotation(method);
        String sql = null;
        /*根据method的注解不同来获取对应的sql语句*/
        if (update != null) {
            sql = update.value();
        } else if (updateProvider != null) {
            sql = providerBuilder(method, args, type);
        }

        return update(sql, method, args);
    }

    private <T> Object update(String sql, Method method, Object[] args) {
        Connection conn = getConnection();
        PreparedStatement ps;
        Map<String, Object> detail = buildSql(sql);
        try {
            ps = excutePS(conn, (String) detail.get(SQL), detail, args);
            ps.executeUpdate();
        } catch (SQLException e) {
            log.warning(e.getMessage());
            throw new GlobalException(e.getMessage());
        }
        return null;
    }


    /**
     * 执行新增操作
     *
     * @param method
     * @param args
     * @return
     */
    private Object insert(Method method, Object[] args, Type type) {
        Insert insert = getInsertAnnotation(method);
        InsertProvider insertProvider = getInsertProviderAnnotation(method);
        String sql = null;
        /*根据method的注解不同来获取对应的sql语句*/
        if (insert != null) {
            sql = insert.value();
        } else if (insertProvider != null) {
            sql = providerBuilder(method, args, type);
        }
        return insert(sql, method, args);
    }

    private Object insert(String sql, Method method, Object[] args) {
        Connection conn = getConnection();
        PreparedStatement ps = null;
        Map<String, Object> detail = buildSql(sql);
//        log.info("insert method sql :" + detail.get(SQL));
        try {
            ps = excutePS(conn, (String) detail.get(SQL), detail, args);
            ps.executeUpdate();
        } catch (SQLException e) {
            log.warning(e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据返回值的类型来进行不同的查询
     *
     * @param method
     * @param args
     * @return
     * @throws SQLException
     */
    private Object select(Method method, Object[] args, Type type) {
        //获取返回值的类型
        Type returnType = method.getReturnType();
        Select select = getSelectAnnotation(method);
        SelectProvider selectProvider = getSelectProviderAnnotation(method);
        String sql = null;
        if (select != null) {
            sql = select.value();
        } else if (selectProvider != null) {
            sql = providerBuilder(method, args, type);
        }
        try {
            if (CollectionUtil.isCollection(returnType)) {
                return selectAll(sql, method, args, type);    //返回值为list
            } else if (ReflectionUtil.isPage(method)) {
                return selectPage(sql, method, args, type);   //返回值为Page
            } else if (ReflectionUtil.isInt(returnType)) {
                return count(sql, method, args);        //返回值为int
            } else {
                return selectOne(sql, method, args, type);    //返回值为单个
            }
        } catch (SQLException e) {
            throw new GlobalException(e.getMessage());
        }
    }

    /**
     * 分页查询
     *
     * @param sql
     * @param method
     * @param args
     * @param <T>
     * @return
     */
    private <T> Page<T> selectPage(String sql, Method method, Object[] args, Type type) throws SQLException {
        //最终结果对象
        Page<T> result = new Page<>();
        //数据库查出来的内容
        List<T> content = new ArrayList<>();
        //解析sql语句
        Map<String, Object> detail = buildSql(sql);
        StringBuilder excuteSql = new StringBuilder((String) detail.get(SQL));
        //数据库连接
        Connection conn = getConnection();
        PreparedStatement ps = null;
        PageSearch pageSearch = PageUtil.getPageSearch(args);
        Integer totalRow = countPageTotalRows((String) detail.get(SQL), detail, args);//总记录数
        Integer pageSize = pageSearch.getPageSize();//每页记录数
        Integer pageNumber = pageSearch.getPageNumber();//第几页
        Integer totalPage = (int) Math.ceil((float) totalRow / pageSize); //总页数
        int paramNum = ((List<String>) detail.get(PARAMS)).size(); //参数的个数
        //开始的条数
        Integer beginRow = (pageNumber - 1) * pageSize;
        String[] orderBy = null;

        if (StringUtil.isNotBlank(pageSearch.getOrderBy())) {
            orderBy = pageSearch.getOrderBy().split(",");
        }
        //排序
        if (!CollectionUtil.isEmpty(orderBy)) {
            excuteSql.append(" order by ");
            excuteSql.append(" ").append(pageSearch.getOrderBy()).append(" ");
            pageSearch.setOrderBy(pageSearch.getOrderBy());
        }


        /* 添加limit n,y 条件 分页的核心,最终结果获取从N条到N+y条数据*/
        T resultTypeInstancec = null;
        excuteSql.append(" limit ?,?");

        try {
            if (isBaseMapper(method)) {
                resultTypeInstancec = (T) ((Class) ((ParameterizedType) type).getActualTypeArguments()[0]).newInstance();
            } else {
                resultTypeInstancec = (T) ReflectionUtil.getListCalssEntity(method);
            }
            Object[] sqlArgs = new Object[args.length - 1];
            int k = 0;
            for (Object arg : args) {
                if (arg.getClass() != PageSearch.class) {
                    sqlArgs[k] = args[k];
                    k++;
                }
            }
            //将参数放到prestatement中
            ps = excutePS(conn, excuteSql.toString(), detail, args);
            //填充limit条件
            ps.setObject(++paramNum, beginRow);
            ps.setObject(++paramNum, pageSize);
            //执行sql语句获取最终结果
            ResultSet rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            while (rs.next()) {
                Map<String, Object> data = new HashMap<>();
                for (int i = 1; i <= columnCount; i++) {
                    data.put(rsmd.getColumnLabel(i), rs.getObject(rsmd
                            .getColumnLabel(i)));
                }
                //将sql执行后的结果集转换为我们想要的对象
                if (haveResult(method)) {
                    content.add((T) toResults(getResultsAnnotation(method), resultTypeInstancec.getClass(), data));
                } else {
                    content.add((T) mapToEntity(resultTypeInstancec.getClass(), data));
                }
            }
        } catch (SQLException | IllegalAccessException | InstantiationException |
                ClassNotFoundException e) {
            log.warning(e.getMessage());
            throw new GlobalException(e.getMessage());
        } finally {
            ps.close();
            conn.close();
        }
        //把参数放到对象中,最后返回前端
        result.setContent(content);
        result.setPageNumber(pageNumber);
        result.setPageSize(pageSize);
        result.setTotalPages(totalPage);
        result.setTotalRows(totalRow);
        return result;
    }

    /**
     * 获取page查询的总数
     *
     * @param pageSql
     * @param detail
     * @param args
     * @return
     * @throws SQLException
     */
    private Integer countPageTotalRows(String pageSql, Map<String, Object> detail, Object[] args) throws SQLException {
        Integer result = null;
        Connection conn = getConnection();
        PreparedStatement ps = null;
        String sql = "select count(*) " + pageSql.substring(pageSql.indexOf("from"));
        if (sql.contains("order")) {
            sql = sql.substring(0, sql.indexOf("order"));
        }
        try {
            Object[] countArgs = new Object[args.length - 1];
            int i = 0;
            for (Object arg : args) {
                if (arg.getClass() != PageSearch.class) {
                    countArgs[i] = args[i];
                    i++;
                }
            }
            ps = excutePS(conn, sql, detail, countArgs);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                result = ((Long) rs.getObject(1)).intValue();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            ps.close();
            conn.close();
        }

        return result;
    }

    /**
     * count查询
     *
     * @param sql
     * @param method
     * @param args
     * @return
     */
    private Integer count(String sql, Method method, Object[] args) throws SQLException {
        Integer result = null;
        Map<String, Object> detail = buildSql(sql);
        Connection conn = getConnection();
        PreparedStatement ps = null;
        String excuteSql = (String) detail.get(SQL);
        log.info("count sql :" + excuteSql);
        try {
            ps = excutePS(conn, excuteSql, detail, args);
            ResultSet rs = ps.executeQuery();

            if (rs.next()) {
                if (rs.getObject(1) == null) {
                    return null;
                }
                Object v = rs.getObject(1);
//                System.out.println(v.getClass().getTypeName());
                if (v.getClass().getTypeName().equals("java.lang.Long")) {
                    result = ((Long) rs.getObject(1)).intValue();
                } else if (v.getClass().getTypeName().equals("java.math.BigDecimal")) {
                    result = ((BigDecimal) rs.getObject(1)).intValue();
                } else {
                    result = ((Integer) rs.getObject(1)).intValue();
                }

            }
        } catch (SQLException e) {
            log.warning(e.getMessage());
            throw new GlobalException(e.getMessage());
        } finally {
            ps.close();
            conn.close();
        }
        return result;
    }


    /**
     * 查询所有
     *
     * @param method
     * @param args
     * @return
     * @throws SQLException
     */
    private <T> List<T> selectAll(String sql, Method method, Object[] args, Type type) throws SQLException {
        List<T> result = new ArrayList<>();
        T resultTypeInstancec = null;
        Map<String, Object> detail = buildSql(sql);
        try {

            if (isBaseMapper(method)) {
                resultTypeInstancec = (T) ((Class) ((ParameterizedType) type).getActualTypeArguments()[0]).newInstance();
            } else {
                resultTypeInstancec = (T) ReflectionUtil.getListCalssEntity(method);
            }
            List<Map<String, Object>> datas = selectAll(detail, method, args);
            if (datas == null) {
                return null;
            }
            /* 判断是否有@Result,有,按照里面的来 */
            for (Map<String, Object> data : datas) {
                if (haveResult(method)) {
                    result.add((T) toResults(getResultsAnnotation(method), resultTypeInstancec.getClass(), data));
                } else {
                    result.add((T) mapToEntity(resultTypeInstancec.getClass(), data));
                }
            }

        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException exception) {
            throw new GlobalException(exception.getMessage());
        }
        return result;
    }


    private List<Map<String, Object>> selectAll(Map<String, Object> detail, Method method, Object[] args) throws SQLException {
        Connection conn = getConnection();
        PreparedStatement ps = null;
        List<Map<String, Object>> datas = null;
        Map<String, Object> data;
        String sql = (String) detail.get(SQL);
        try {
            ps = excutePS(conn, sql, detail, args);
            ResultSet rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            datas = new ArrayList<>();
            while (rs.next()) {
                data = new HashMap<>();
                for (int i = 1; i <= columnCount; i++) {
                    data.put(rsmd.getColumnLabel(i), rs.getObject(rsmd
                            .getColumnLabel(i)));
                }
                datas.add(data);
            }
        } catch (SQLException e) {
            log.warning(e.getMessage());
            throw new GlobalException(e.getMessage());
        } finally {
            ps.close();
            conn.close();
        }
        return datas;
    }

    /**
     * 查询单条记录
     *
     * @param method
     * @param args
     * @param <T>
     * @return
     * @throws SQLException
     */

    private <T> T selectOne(String sql, Method method, Object[] args, Type type) throws SQLException {
        Map<String, Object> detail = buildSql(sql);
        T result = null;
        try {
            if (isBaseMapper(method)) {
                result = (T) ((Class) ((ParameterizedType) type).getActualTypeArguments()[0]).newInstance();
            } else {
                result = (T) method.getReturnType().newInstance();
            }
            Map<String, Object> data = selectOne(detail, method, args);
            if (data == null) {
                return null;
            }
            if (haveResult(method)) {
                result = (T) toResults(getResultsAnnotation(method), result.getClass(), data);
            } else {
                result = (T) mapToEntity(result.getClass(), data);
            }
        } catch (IllegalAccessException | InstantiationException e) {
            throw new GlobalException(e.getMessage());
        }
        return result;
    }

    private <T> Map<String, Object> selectOne(Map<String, Object> detail, Method method, Object[] args) throws SQLException {
        String sql = (String) detail.get(SQL);
        Connection conn = getConnection();
        PreparedStatement ps = null;
        Map<String, Object> data = null;
        try {
            ps = excutePS(conn, sql, detail, args);
            ResultSet rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                data = new HashMap<>();
                for (int i = 1; i <= columnCount; i++) {
                    data.put(rsmd.getColumnLabel(i), rs.getObject(rsmd
                            .getColumnLabel(i)));
                }
            }
        } catch (SQLException e) {
            log.warning(e.getMessage() + "for sql :" + sql);
            throw new GlobalException(e.getMessage());
        } finally {
            ps.close();
            conn.close();
        }
        return data;
    }

    /**
     * 将带有#{}的sql语句解析
     *
     * @param sql
     * @return
     */
    private Map<String, Object> buildSql(String sql) {
        int end = -1;
        List<String> params = new ArrayList<>();
        Map<String, Object> result = new HashMap<>();
        StringBuilder sqlBuilder = new StringBuilder(sql);
        for (int begin = sqlBuilder.indexOf("#{"); begin != -1; begin = sqlBuilder.indexOf("#{", begin + 1)) {
            end = sqlBuilder.indexOf("}", end + 1);
            params.add(sqlBuilder.substring(begin + 2, end));
            sqlBuilder.replace(begin, end + 1, "?");
            end = -1;
        }
        result.put(SQL, sqlBuilder.toString());
        result.put(PARAMS, params);
        return result;


    }

    /**
     * 将参数放进ps里
     *
     * @param detail
     * @param args
     * @return
     */
    private PreparedStatement excutePS(Connection conn, String sql, Map<String, Object> detail, Object[] args) throws SQLException {
        PreparedStatement ps;
        try {
            ps = conn.prepareStatement(sql);
            if (((List<String>) detail.get(PARAMS)).size() == 0 && (args == null || args.length == 0)) {
                return ps;
            }
            if (args == null || args.length <= 0) {
                return ps;
            } else {
                for (int i = 0; i < args.length; i++) {
//                    System.out.println(args[i].getClass());
                    if (ReflectionUtil.isPrimitive(args[i])) {
                        ps.setObject(i + 1, args[i]);
                        continue;
                    } else if (args[i].getClass().getName().equals("java.util.ArrayList")) {
                        List list = (ArrayList) args[i];
                        for (int k = 0; k < list.size(); k++) {
                            ps.setObject(++i, list.get(k));
                        }

                    } else {
                        int k = i;
                        for (String param : (List<String>) detail.get(PARAMS)) {
                            if (ReflectionUtil.haveField(args[i], param)) {
                                Field paramField = ReflectionUtil.getFieldByName(args[i], param);
                                paramField.setAccessible(true);
                                ps.setObject(k + 1, paramField.get(args[i]));
                                k++;
                            }
                        }
                        continue;
                    }
                }

            }
        } catch (IllegalAccessException | SQLException e) {
            log.warning(e.getMessage());
            throw new GlobalException(e.getMessage());

        }
        return ps;

    }

}